export class RSACrypto {
  private publicKey: CryptoKey | null = null
  private privateKey: CryptoKey | null = null

  /**
   * 生成RSA密钥对
   */
  async generateKeyPair(): Promise<{ publicKey: string; privateKey: string }> {
    try {
      const keyPair = await window.crypto.subtle.generateKey(
        {
          name: 'RSA-OAEP',
          modulusLength: 2048, // RSA密钥长度
          publicExponent: new Uint8Array([1, 0, 1]), // 65537
          hash: 'SHA-256',
        },
        true, // 是否可导出
        ['encrypt', 'decrypt'],
      )

      this.publicKey = keyPair.publicKey
      this.privateKey = keyPair.privateKey

      // 导出密钥为PEM格式
      const publicKeyPem = await this.exportKeyToPem(keyPair.publicKey, 'public')
      const privateKeyPem = await this.exportKeyToPem(keyPair.privateKey, 'private')

      return {
        publicKey: publicKeyPem,
        privateKey: privateKeyPem,
      }
    } catch (error) {
      console.error('RSA密钥生成失败:', error)
      throw new Error(`RSA密钥生成失败: ${error}`)
    }
  }

  /**
   * 导出密钥为PEM格式
   */
  private async exportKeyToPem(key: CryptoKey, type: 'public' | 'private'): Promise<string> {
    const format = type === 'public' ? 'spki' : 'pkcs8'
    const exported = await window.crypto.subtle.exportKey(format, key)
    const exportedAsBase64 = btoa(String.fromCharCode(...new Uint8Array(exported)))

    const pemHeader =
      type === 'public' ? '-----BEGIN PUBLIC KEY-----\n' : '-----BEGIN PRIVATE KEY-----\n'
    const pemFooter =
      type === 'public' ? '\n-----END PUBLIC KEY-----' : '\n-----END PRIVATE KEY-----'

    return pemHeader + exportedAsBase64.match(/.{1,64}/g)?.join('\n') + pemFooter
  }

  /**
   * 从PEM格式导入密钥
   */
  async importKeyFromPem(pemKey: string, type: 'public' | 'private'): Promise<void> {
    try {
      const pemContents = pemKey
        .replace(/-----BEGIN (PUBLIC|PRIVATE) KEY-----/, '')
        .replace(/-----END (PUBLIC|PRIVATE) KEY-----/, '')
        .replace(/\s/g, '')

      const binaryKey = Uint8Array.from(atob(pemContents), (c) => c.charCodeAt(0))

      const algorithm = {
        name: 'RSA-OAEP',
        hash: 'SHA-256',
      }

      if (type === 'public') {
        this.publicKey = await window.crypto.subtle.importKey('spki', binaryKey, algorithm, true, [
          'encrypt',
        ])
      } else {
        this.privateKey = await window.crypto.subtle.importKey(
          'pkcs8',
          binaryKey,
          algorithm,
          true,
          ['decrypt'],
        )
      }
    } catch (error) {
      console.error(`RSA${type}密钥导入失败:`, error)
      throw new Error(`RSA${type}密钥导入失败: ${error}`)
    }
  }

  /**
   * RSA加密
   * @param plaintext 明文
   * @returns 加密后的Base64字符串
   */
  async encrypt(plaintext: string): Promise<string> {
    try {
      if (!plaintext) {
        throw new Error('明文不能为空')
      }

      if (!this.publicKey) {
        throw new Error('公钥未设置')
      }

      const encoder = new TextEncoder()
      const data = encoder.encode(plaintext)

      const encrypted = await window.crypto.subtle.encrypt(
        {
          name: 'RSA-OAEP',
        },
        this.publicKey,
        data,
      )

      // 转换为Base64
      const encryptedArray = new Uint8Array(encrypted)
      return btoa(String.fromCharCode(...encryptedArray))
    } catch (error) {
      console.error('RSA加密失败:', error)
      throw new Error(`RSA加密失败: ${error}`)
    }
  }

  /**
   * RSA解密
   * @param ciphertext 密文(Base64编码)
   * @returns 解密后的明文
   */
  async decrypt(ciphertext: string): Promise<string> {
    try {
      if (!ciphertext) {
        throw new Error('密文不能为空')
      }

      if (!this.privateKey) {
        throw new Error('私钥未设置')
      }

      // 从Base64解码
      const encryptedData = Uint8Array.from(atob(ciphertext), (c) => c.charCodeAt(0))

      const decrypted = await window.crypto.subtle.decrypt(
        {
          name: 'RSA-OAEP',
        },
        this.privateKey,
        encryptedData,
      )

      const decoder = new TextDecoder()
      return decoder.decode(decrypted)
    } catch (error) {
      console.error('RSA解密失败:', error)
      throw new Error(`RSA解密失败: ${error}`)
    }
  }
}
